/**
 * @file Tetromino
 * 
 * The tetromino is an array that contains the tetroboxes to be eliminated.  
 * This class functions as a container which brings the boxes down to the 
 * last level.  Beyond providing a convenient way to create and interact
 * with tetroboxes, this class doesn't do much else.
 */
package sprite;

import java.awt.event.KeyEvent;
import java.awt.Point;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * @class Tetromino
 * 
 * The Tetromino is a small grid containing information about blocks
 * contained inside of it.  A tetromino can be moved and rotated by
 * the user to place the tetromino according to their desire onto the
 * puzzle grid.
 */
public class Tetromino {

  /**
   * The tetrobox is the responsible for maintaining the position and 
   * placement of the tetromino blocks
   */
  protected Tetrobox[][] tetrosquare;	
  
  /**
   * A LinkedList containing the list of valid tetrobox grids
   */
  protected LinkedList<Tetrobox[][]> rotations;
  private Iterator<Tetrobox[][]> rotation_iter;
  
  /**
   * These variables represent the movement of the tetromino
   */
  boolean moveLeft = false;
  boolean moveRight = false;
  boolean moveDown = true;
  
  /**
   * Height, in grid units, of the tetromino
   */
  public final int WIDTH  = 4;
  public final int HEIGHT = 4;
  
  /**
   * Width of OCCUPIED tetroboxes within tetromino
   */
  private int tetroHeight;
  private int tetroWidth;
  
  /**
   * The grid point at which this tetromino resides
   */
  public Point point;
  
  /**
   * Default constructor
   * 
   * Creates a new, empty tetromino at the top left corner of the grid
   */
  protected Tetromino() {
	  
    point = new Point(0, 0);
  }
  
  /**
   *  Copy constructor
   *  
   *  Crates a copy of the tetromino fed in.
   */
  public Tetromino(Tetromino t) {
  
    // Ensure tetrosquare is null (initialized later)
    this.tetrosquare     = null;
    
    // Copy the rotations list over
    this.setRotations(t.rotations);
    
    // Start static by default
    this.moveLeft        = false;
    this.moveRight       = false;
    this.moveDown        = false;
    
    // Clone the location of the tetro
    this.setPoint(t.getPoint());
    
    // Spin up the new iterator to match the old one
    do {
    
      this.rotateR();
    } while(tetrosquare != t.tetrosquare);
    
    // Get the dimensions from the tetro
    this.setTetroHeight(t.getTetroHeight());
    this.setTetroWidth(t.getTetroWidth());
  }
  
  /**
   * Alt Constructor 1
   * 
   * Creates a new tetromino of the input width and height (both in px)
   * and places it at the top left corner of the grid
   * 
   * @param int width
   * 
   *   The width, in grid units, of the tetromino
   *   
   * @param int height 
   * 
   *   The height, in grid units, of the tetromino
   */
  public Tetromino(int width, int height) {
  
    tetrosquare = new Tetrobox[WIDTH][HEIGHT];
    point = new Point(0, 0);
  }
  
  /**
   * Alt Constructor 2
   * 
   * Insert a tetrogrid into the tetromino, indicating the width and height of the
   * new array
   * 
   * @param Tetrobox[][] boxes
   * 
   *   The grid which contains the tetromino pattern with which this 
   *   tetromino will be initialized
   * 
   * @param int width
   * 
   *   The width, in elements, of the boxes array (columns)
   *   
   * @param int height
   * 
   *   The height, in elements, of the boxes array (rows)
   */
  public Tetromino(Tetrobox[][] boxes, int width, int height) {
	  
	  rotations = new LinkedList<Tetrobox[][]>();
    rotations.add(boxes);
    rotation_iter = rotations.iterator();
    
	  setTetros(rotations.getFirst());
    initializeTetrominoGrid(width, height);
    point = new Point(0, 0);
    
  }
  
  /**
   * Alt Constructor 3
   * 
   * Insert a series of tetrogrids into the tetromino, indicating the width and height of the
   * new array size
   * 
   * @param LinkedList<Tetrobox[][]>
   * 
   *   A series of tetrobox grids which comprise the series of valid rotations.
   *   The series should be in 90 degree increments, one after the next.
   *   The list should have at least one valid tetro in it.
   * 
   * @param int width
   * 
   *   The width, in elements, of the boxes array (columns)
   *   
   * @param int height
   * 
   *   The height, in elements, of the boxes array (rows)
   */
  public Tetromino(LinkedList<Tetrobox[][]> rotations,  int width, int height) {
	  
    initializeTetrominoGrid(width, height);
    setTetros(rotations.getFirst());
    point = new Point(0, 0);
    
    this.rotations = rotations;
    this.rotation_iter = rotations.iterator();
  }
  /**
   * Set the active tetrobox grid
   * 
   * @param boxes
   * 
   *   A 2 dimensional Tetrobox Array.  Indicates the pattern
   *   of the tetroboxes that comprise the tetrobox
   */
  public void setTetros(Tetrobox[][] boxes) {
	  
	  this.tetrosquare = boxes;
  }
  
  /**
   * Load in a rotation set of tetromino grids
   * 
   * @param LinkedList<Tetrobox[][]> rotations
   * 
   *   A list of tetrogrids which represent 90 degree turns from one another
   */
  public void setRotations(LinkedList<Tetrobox[][]> rotations) {
  
    this.rotations = rotations;
    this.rotation_iter = rotations.iterator();
    setTetros(rotation_iter.next());
  }
  
  /**
   * Set the Point at which the Tetromino's
   * top left corner resides
   * 
   * @param point
   *   A Point which contains the x and y
   *   coordinates of the Tetromino's top left
   *   brick.  Measured in grid units.
   */ 
  public void setPoint(Point point) {
  
    this.point = point;
  }
  
  /**
   * Set the X coordinate of the left corner of the 
   * Tetromino.
   * 
   * @param x
   * 
   *   The x coordinate of the left brick.
   *   Measured in grid units.
   */
  public void setPointX(int x) {
  
    this.point.x = x; 
  }
  
  /**
   * Set the Y coordinate of the left corner of the 
   * Tetromino.
   * 
   * @param y
   * 
   *   The y coordinate of the left brick.
   *   Measured in grid units.
   */
  public void setPointY(int y) {
  
    this.point.y = y;
  }
  
  /**
   * Set the width of the *visible* tetro pieces
   * 
   * @param width
   * 
   *   The width is defined to indicate how wide, 
   *   in grid units, the actual visible tetromino
   *   is (as opposed to the size of the grid, which 
   *   is always a constant size).
   */
   
  protected void setTetroWidth(int width) {
  
    this.tetroWidth = width;
  }
  
  /**
   * Set the height of the *visible* tetro pieces
   * 
   * @param height
   * 
   *   The height is defined to indicate how high, 
   *   in grid units, the actual visible tetromino
   *   is (as opposed to the size of the grid, which 
   *   is always a constant size).
   */
  protected void setTetroHeight(int height) {
  
    this.tetroHeight = height;
  }
  
  /**
   * Returns the tetrobox residing at the current
   * gridpoint in the tetromino.  Measured in grid
   * units.  Relative to the top left of the tetromino
   * (not the top left of the board grid).
   * 
   * @param int x
   * 
   *   The x coordinate of the tetrosquare, relative to 
   *   the top left of the tetromino grid
   */
  public Tetrobox getTetro(int x, int y) {
	  
	  return tetrosquare[x][y];
  }
 
  /**
   * Returns the Point of the top-left corner grid point
   * of the tetromino, relative to the board grid
   * 
   * @return Point
   * 
   *   The Point of the top-left corner.  In .x & .y notation
   */
  public Point getPoint() {
  
    return point;
  }
  
  
  /**
   * Returns the X coordinate of the top-left tetrobox 
   * of the Tetromino.  Relative to the top-left of the 
   * game board
   * 
   * @return int
   *   
   *   The x coordinate of the tetromino's base
   */
  public int getPointX() {
  
    return point.x;
  }
  
  /**
   * 
   * Returns the Y coordinate of the top-left tetrobox
   * of the Tetromino.  Relative to the top-left of the 
   * game board
   * 
   * @return int
   * 
   *   The y coordinate of the tetromino's base
   */
  public int getPointY() {
  
    return point.y; 
  }
  
  /**
   * Return the tetrobox grid belonging 
   * to the Tetromino
   * 
   * @return Tetrobox[][]
   * 
   *   The tetrobox grid defining the tetromino
   */
  public Tetrobox[][] getTetros() {
	  
	 return tetrosquare; 
  }
  
  /**
   * The height of the visible tetromino grid
   * 
   * @return int
   * 
   *   The height of the visible grid of the Tetromino
   *   (as opposed to the height of the entire tetromino
   *   grid, which is constant)
   */
  public int getTetroHeight() {
  
    return this.tetroHeight;
  }
  
  /**
   * The width of the visible tetromino grid
   * 
   * @return int
   * 
   *   The width of the visible grid of the Tetromino
   *   (as opposed to the width of the entire tetromino
   *   grid, which is constant)
   */
  public int getTetroWidth() {
  
    return this.tetroWidth;
  }
  
  /**
   * Indicates whether or not the block wants to move left
   * 
   * @return boolean
   *   Will return true if the tetromino wishes to move left.
   *   It will return false otherwise.
   */
  public boolean isMoveLeft() {
	  
	 return moveLeft; 
  }
  
  /**
   * Indicates whether or not the block wants to move right
   * 
   * @return boolean
   *   Will return true if the tetromino wishes to move right.
   *   It will return false otherwise.
   */
  public boolean isMoveRight() {
	  return moveRight;
  }
  
  /**
   * Indicates whether or not the block wants to move down.
   * A tetromino that is able to move down must always do so.
   * 
   * @return boolean
   *   Will return true if the tetromino wishes to move down.
   *   It will return false otherwise.
   */
  public boolean isMoveDown() {
	  
	  return moveDown;
  }
  
  /**
   * Commands the tetromino to stop moving down.  Used as an indicator 
   * to the Board that the tetromino is no longer active.
   */
  public void stopMoveDown() {
	  
	  moveDown = false;
  }
  
  /**
   * Commands the tetromoino to forget its wish to move left
   */
  public void stopMoveLeft() {
  
    moveLeft = false;
  }
  
  /**
   * Commands the tetromino to forget its wish to move right
   */
  public void stopMoveRight() {
  
    moveRight = false;
  }
  
  /**
   * Command the tetromino to begin its descent
   */
  public void startMoveDown() {
	  
	  moveDown = true;
  }
  
   
  /**
   * Listens for keyboard signals from the board and implements
   * logic as necessary
   */
  public void keyPressed(KeyEvent e) {
	  
	  // If left was pressed...
	  if (e.getKeyCode() == KeyEvent.VK_LEFT) {
		  
		  // Move the x left, relative to the grid topleft
		  setPointX(getPointX() - 1);
	  }
	  
	  // If the right key was pressed...
	  if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
		  
		  // Move the x right, relative to the grid topleft
		  setPointX(getPointX() + 1);
	  }
	  
	  //If the up key was pressed...
	  if (e.getKeyCode() == KeyEvent.VK_UP) {
	  
	    // Rotate that bad boy right 90 degrees!
	    rotateR();
	  }
  }
  
  /**
   * Rotate the tetromino 90 degrees to the right
   */
  protected void rotateR() {
  
    
    if (!rotation_iter.hasNext()) {
    
      rotation_iter = rotations.iterator();
    }
    
    if (rotation_iter.hasNext()) {
 
      tetrosquare = rotation_iter.next();
    }
    
    int tetroHeight = getTetroHeight();
    int tetroWidth  = getTetroWidth();
    
    setTetroWidth(tetroHeight);
    setTetroHeight(tetroWidth);
  }
  
  /**
   * Initialize the tetromino grid with empty, invisble tetrogrids.
   */
  protected void initializeTetrominoGrid(int width, int height) {
  
    for (int tg_x = 0 ; tg_x < WIDTH ; tg_x++) {
    
      for (int tg_y = 0 ; tg_y < HEIGHT ; tg_y++) {
      
        tetrosquare[tg_x][tg_y] = new Tetrobox(tg_x, tg_y, width, height);
      }
    }
  }
  
  /**
   * Initialize the tetromino grid with empty, invisble tetrogrids.
   */
  protected Tetrobox[][] newTetrominoGrid(int width, int height, int gridWidth, int gridHeight) {
  
    Tetrobox[][] tetrosquare = new Tetrobox[gridWidth][gridHeight];
  
    for (int tg_x = 0 ; tg_x < WIDTH ; tg_x++) {
    
      for (int tg_y = 0 ; tg_y < HEIGHT ; tg_y++) {
      
        tetrosquare[tg_x][tg_y] = new Tetrobox(tg_x, tg_y, width, height);
      }
    }
    
    return tetrosquare;
  }
}
